package com.example.lottery.service.mq;


import cn.hutool.core.date.DateUtil;
import com.example.lottery.common.exception.ServiceException;
import com.example.lottery.common.utils.JacksonUtil;
import com.example.lottery.common.utils.MailUtil;
import com.example.lottery.controller.param.DrawPrizeParam;
import com.example.lottery.dao.datobject.ActivityPrizeDO;
import com.example.lottery.dao.datobject.WinningRecordDO;
import com.example.lottery.dao.mapper.ActivityPrizeMapper;
import com.example.lottery.dao.mapper.WinningRecordMapper;
import com.example.lottery.service.DrawPrizeService;
import com.example.lottery.service.activitystatus.ActivityStatusManager;
import com.example.lottery.service.dto.ConvertActivityStatusDTO;
import com.example.lottery.service.enums.ActivityPrizeStatusEnum;
import com.example.lottery.service.enums.ActivityPrizeTiersEnum;
import com.example.lottery.service.enums.ActivityStatusEnum;
import com.example.lottery.service.enums.ActivityUserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.example.lottery.common.config.DirectRabbitConfig.QUEUE_NAME;

//定义消费者类
@Component
@RabbitListener(queues = QUEUE_NAME)
@Slf4j
public class MqReceiver {
    @Autowired
    private DrawPrizeService drawPrizeService;
    @Autowired
    private ActivityStatusManager activityStatusManager;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @RabbitHandler
    public void process(Map<String,String> message){
        log.info("MQ成功接收到消息,message:{}",
                JacksonUtil.writeValueAsString(message));
        String paramString=message.get("messageData");
        DrawPrizeParam param=JacksonUtil.readValue(paramString,DrawPrizeParam.class);

        try{
            //处理抽奖的流程
            //这里私信队列测试消息堆积
            //这里会出现死信队列的盲等 不易看到堆积现象
//            if(true){
//                throw new RuntimeException();
//            }

            //检验抽奖请求是否有效
            //这里可能前端传递两个param 刚好param为最后一个奖品
            //处理完param1：活动完成 奖品完成
            //处理param2:检验不通过，如果抛出异常，就会回滚， 故不让checkDrawPrizeParam抛出异常即可
            if(!drawPrizeService.checkDrawPrizeParam(param)){
                return;
            }
            //状态扭转（这里使用了责任链模式 和 策略模式 重要！！）
            statusConvert(param);
            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList=drawPrizeService.saveWinnerRecords(param);

            //测试回滚，这里先暂时不使用死信队列
//            if(true){
//                int a=10/0;
//            }
            //通知中奖者（邮箱通知）  这个发送失败 也不影响 故不需要回滚
            noticePrize(winningRecordDOList);

        }catch (ServiceException e){
            log.error("处理MQ 消息异常!{}:{}",e.getCode(),e.getMessage());
            //需要保持事务一致性（回滚）
            rollback(param);
            //抛出异常:进行消息重试 重试5此后 消息堆积在死信队列里面
            //等到异常解决 死信队列重新发送消息给正常队列
            throw e;
        }catch (Exception e){
            log.error("处理MQ 消息异常!",e);
            rollback(param);
            throw e;
        }
    }

    /**
     * 处理抽奖异常的回滚行为:恢复MQ执行statusConvert(param);  List<WinningRecordDO> winningRecordDOList=drawPrizeService.saveWinnerRecords(param）
     * 的状态
     * @param param
     */
    private void rollback(DrawPrizeParam param) {
        //判断活动 活动奖品 活动人员状态 是否需要回滚
        if(!statusNeedRollback(param)){
            //进来这里说明 活动 活动奖品 活动人员状态根部就没有转化，就是没有执行到statusConvert(param);
            return;
        }
        //走到这里 说明状态改变了 因为异常 需要回滚
        rollbackStatus(param);

        //判断 是否需要回滚中奖者名单
        if(!winnerNeedRollback(param)){
            return;
        }
        rollbackWinner(param);
    }

    /**
     * 回滚中奖记录
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }

    /**
     * 判断是否需要回滚中奖记录
     * @param param
     * @return
     */
    private boolean winnerNeedRollback(DrawPrizeParam param) {
        int count=winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        return count>0;
    }

    /**
     * 进行状态的回滚
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);
    }

    /**
     * 判断活动 活动人员 活动奖品是否需要回滚
     * @param param
     * @return
     */
    private boolean statusNeedRollback(DrawPrizeParam param) {
        //由于扭转状态时 执行handlerEvent 保持了事务的一致性
        //再加上活动状态 依赖 活动奖品状态
        //故选择判断活动奖品状态是否扭转 来判断活动 活动奖品 活动人员状态是否全部扭转
        ActivityPrizeDO activityPrizeDO=activityPrizeMapper.selectByAPId(param.getActivityId(), param.getPrizeId());
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name());
    }

    /**
     * 邮箱通知中奖者
     * @param winningRecordDOList
     */
    private void noticePrize(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，不用发邮件！");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "： " + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendMailToPrize(winningRecordDO.getWinnerEmail(), context);
        }
    }


    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);
        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }


}
